/**
 * @brief comment block, escape seq and error 
 * 
*/
#ifndef _LEXER_C_HPP
#define _LEXER_C_HPP 1

#include<iostream>
#include<fstream>
#include<string>
#include<map>
#include<list>
/**
 * keyword:
 * - if-else
 * - while
 * - sign # ; () {} <> = "" relOp :: cond op
 * - 
*/
enum signTable{
    sharp = 0,semicolon = 1, colon = 2,leftBracket = 3 ,rightBracket,// # ; : < > 0-4
    leftParenthesis,rightParenthesis,quote,assign,//( ) " " = 5-8
    EQ,LT,LE,GT,GE,// == < <= > >= 9-13
    add,minus,mult,divi,mod,// + - * / % 14-28 slash AKA /
    Std,//29
    Lit,//literal 30
    reIn,reOut,// >> <<  31 32
    If,Else,While,// if else while 33-35
    Include,// 36
    // ,Null//34 for Error
    backSlash// for comment and escape seq
};
enum lexicalState{
    Null,Comment,String,Character,BlockComment,Identifier,Number,Sign
};
/**
 * @brief close the current state or set the new state
*/
lexicalState stateShift(lexicalState ls,lexicalState change){
    return ls==Null?change:Null;
}
struct Pair{
    std::string s;
    signTable val;
};
namespace var{
    std::map<std::string,signTable> st;
    std::map<signTable,std::string>s2str;
    std::map<std::string,std::string>type;
}
namespace lexeme_process{
    enum scale{dec,bin,oct,hex};
    bool is_quote(char const c){
        return c=='\"'||c=='\'';
    }
    bool all_digits(std::string const&str,size_t st=0,scale sc=dec){
        for(;st!=str.size();++st){
            switch(sc){
                case dec:if(!std::isdigit(str[st])) return false;   break;
                case bin:if('0'!=str[st]||'1'!=str[st]) return false;   break;
                case oct:if(str[st]>='8'||str[st]<'0') return false;   break;
                case hex:if(!std::isxdigit(str[st])) return false;   break;
            }
        }return true;
    }
    bool escape_sequence_validate(std::string const&str){// str[0] == '\\'
        if(str.size()==1)return false;
        else if(std::isdigit(str[1])){// 8 oct
            if(str.size()==4&&str[1]>1)return false;// \177
            return all_digits(str,1,oct);
        }else if(str[1]=='x'){// 16 hex
            if(str.size()<3||(str.size()==4&&str[2]>'7'))return false;// \x7f
            return all_digits(str,2,hex);
        }else if(str.size()==2&&is_quote(str[1])) return true;
        else return true;// don't care about the details now
        return false;
    }
    bool char_validate(std::string const&str){
        if(str.size()==0||str.size()>4)return false;
        else if(str[0]=='\\'){// \177|\0177 \x7f escape sequence
            return escape_sequence_validate(str);
        }else if(str.size()>1)return false;
        else return true;
    }
    /**
     * @brief get a char safely from the source string
     * @warning src[start-1] must be '\'' AKA single quotation mark
     * @return the end position of the char in the string or start stands for err
     * so that means src[get_char()]=='\'' or error
    */
    size_t get_char(size_t start,std::string const&src,std::string&dst){// '(\\(.|x[0-7]?[0-9a-fA-F]|0?1?[0-7]{0,2})|A_ASCII_LETTER)'
        size_t ptr=start;
        char next;
        while(next=src[++ptr]){
            if(next=='\\'){
                if(ptr==src.size())return start;// error
                else if(ptr!=src.size()&&src[ptr]=='\''){
                    dst+="\\\'";
                    ++ptr;
                }else dst+='\\';
            }
            else if(next=='\''){// end of a char
                char_validate(dst);
                return ptr;
            }else dst+=next;
        }return start;// err
    }
    size_t getLiteralString(size_t p,std::string const&str,std::string&dst){
        size_t ptr=p;char next=str[++ptr];
        while(next){
            if(ptr!=str.size()&&next=='\\'){
                switch(str[ptr+1]){
                    case '\"': case '\'':
                        dst=dst+next+str[ptr++];continue;
                }
            }else if(next=='\"'){
                return ptr;
            }
            dst+=next;
            next=str[++ptr];
        }return ptr;
    }
    size_t getIdentifier(size_t p,std::string const&str,std::string&dst){// [_A-Za-z][_A-Za-z0-9]*
        size_t ptr=p;char next=str[++ptr];
        while(next&&(std::isalpha(next)||std::isdigit(next)||'_'==next)){// identifier
            dst+=next;
            next=str[++ptr];
        }//std::cout<<dst<<'\t';
        return ptr;
    }
    size_t getNumber(size_t p,std::string const&str,std::string&dst){// [0-9]+(\.[0-9]+)?
        char next;size_t st=p;
        while(p!=str.size()&&(std::isdigit(next=str[++p])||next=='.')){
            if(next=='.'){
                if(p!=str.size()&&std::isdigit(str[p+1])){
                    dst+='.';
                }
                else return st;// error
            }
            else dst+=next;
        }return p;
    }
}
class lexer_c{
    // data
    std::string file,name,blk0,blk1;
    // static std::map<std::string,signTable> st;
    bool turn,debug;// 0 - blk0  1 - blk1
    size_t lines,byte;
    std::list<std::string>lexemes;
    public:
        lexer_c(std::string const& str,std::string const& n,bool d=false):file(str),name(n),lines(1),byte(1),debug(d){
            // blk0=str.substr(0,4096);
            // blk1=str.substr(4096,4096);// double cache
        }
        lexer_c(std::string const&file,bool d=false);
        static void reload();
        [[deprecated]]void run();// first demo
        lexer_c& execute();
        std::string err(std::string const&,bool noterminate=false,bool Short=false);
        std::string err(size_t line,size_t byte,std::string const&,bool noterminate=false,bool Short=false);
        void show(){
            for(auto&&it:lexemes){
                std::cout<<"< "+it+" , "+getTokenType4out(it)+" >"<<std::endl;
            }
        }
    private:
        /**
         * @param p the first position of the space ALSO isspace(str[p])
         * @return the first char after the space
        */
        size_t skipSpace(size_t p,std::string const&str){
            char next;
            while(next=str[++p]){
                if(!std::isspace(next)){return p;}// position after processing
                else if(next=='\n'){// line counting
                    byte=0;
                    ++lines;
                }
                else ++byte;
            }return str.size();// the end of the string
        }
        /**
         * @brief the position should be the first char after the start sign
         * However, it doesn't matter
        */
        size_t skipBlockComment(size_t p,std::string const&str);// str.substr(p-2,2)=="//"
        size_t skipLineComment(size_t p,std::string const&str);// str.substr(p-2,2)=="/*"
        // size_t composedSign(size_t p,std::string const&str);// <= >= == :: != ->
        std::string getTokenType4out(std::string const&str);
};
lexer_c::lexer_c(std::string const&fileName,bool d):name(fileName),debug(d),lines(1),byte(1){
    std::ifstream Ifs(fileName,std::ios::in);
    Ifs>>std::noskipws;
    char ch;
    if(Ifs.is_open()){
        while(!Ifs.eof()){
            Ifs>>ch;
            if(Ifs.eof())break;
            this->file+=ch;
        }Ifs.close();
    }
}
std::string lexer_c::err(size_t l,size_t b,std::string const&e,bool noterminate,bool Short){
    std::string locate=name+":"+std::to_string(l)+":"+std::to_string(b)+" ",
        // locate="Lines : "+std::to_string(l)+"  "+"bytes : "+std::to_string(b)+"\t",
        Msg="\033[40;31m"+e+"\033[0m";
    std::string f=(Short?"":locate)+Msg;
    std::cerr<<f;
    if(!Short)std::cerr<<std::endl;
    if(!noterminate)std::terminate();
    return f;
}
std::string lexer_c::err(std::string const&e,bool noterminate,bool Short){
    return this->err(this->lines,this->byte,e,noterminate,Short);
}
void lexer_c::reload(){
    var::st["#"]=sharp;
    var::st[";"]=semicolon;
    var::st[":"]=colon;
    var::st["{"]=leftBracket;
    var::st["}"]=rightBracket;
    
    var::st["("]=leftParenthesis;
    var::st[")"]=rightParenthesis;
    var::st["\""]=quote;
    var::st["="]=assign;

    var::st["=="]=EQ;var::st["<"]=LT;var::st["<="]=LE;var::st[">"]=GT;var::st[">="]=GE;
    var::st["+"]=add;var::st["-"]=minus;var::st["*"]=mult;var::st["/"]=divi/* or slash */;var::st["%"]=mod;
    var::st["std"]=Std;var::st["if"]=If;var::st["else"]=Else;var::st["while"]=While;
    var::st["include"]=Include;var::st["<<"]=reOut;var::st[">>"]=reIn;var::st["\\"]=backSlash;
    for(auto&&it:var::st){
        var::s2str[it.second]=it.first;
    }
    var::type["::"]=var::type["=="]=var::type["<="]=var::type[">="]=var::type[">>"]=var::type["<<"]="sign";
    var::type["if"]=var::type["else"]=var::type["while"]=var::type["include"]="keyword";
    var::type["int"]=var::type["double"]="type";
    var::type["std"]="namespace";
}
std::string lexer_c::getTokenType4out(std::string const&str){
    try{
        auto&&item=var::type.at(str);
        return item;
    }catch(...){
        if(std::ispunct(str[0]))return "sign";
        else if(str[0]=='\\')return "escape sequence";// escape sequence
        return std::isdigit(str[0])?"number":"identifier";
    }
}
void lexer_c::run(){
    lexer_c::reload();
    char peek,next;
    std::string cur;
    // std::map<std::string,signTable>lp;
    for(size_t pos=0;pos<file.size();){
        peek=file[pos];
        if(std::isspace(peek)){pos=skipSpace(pos,file);continue;}// space
        else if(std::ispunct(peek)){// punct
            next=file[pos+1];
            switch(peek){
            case '\"':
                ++pos;
                while(!(next=='\"'||pos==file.size())){
                    cur+=next;
                    next=file[++pos];
                }pos++;
                break;
            case ':':cur=":";
                if(next==':')
                    cur+=file[++pos];
                ++pos;
                break;
            case '>':
                if(next=='>'){
                    cur=">>";
                    pos+=2;
                    break;
                }
            case '<':
                if(next=='<'){
                    cur="<<";
                    pos+=2;
                    break;
                }
            case '=':
                if(next=='='){
                    cur=peek+"=";
                    pos+=2;
                    break;
                }
            case '_':// _[_A-Za-z0-9]*
                cur="_";
                while(std::isalpha(next)||std::isdigit(next)||next=='_'){// identifier
                    cur+=next;
                    next=file[++pos];
                }break;
            case '/':{// comment
                int cl=lines;int bt=byte;
                if(next=='/'){// line comment
                    ++this->lines;byte=0;
                    while(file[pos++]!='\n');
                }
                else if(next=='*'){// block comment
                    while(next=file[++pos]){
                        bool end=false;
                        switch(next){
                            case '\n':
                                ++lines;byte=0;break;
                            case '*':// /**/ block comment
                                end=true;
                            case '/':// // line comment
                                if(pos!=file.size()&&file[pos+1]=='/'){
                                    if(!end){
                                        ++this->lines;byte=0;
                                        while(file[pos++]!='\n');
                                    }
                                    continue;
                                }else{++byte;}
                                break;
                        }
                    }
                    if(pos==file.size()){//error
                        this->err(cl,bt,"unclosed comment");
                    }
                }
            }
            default:cur=peek;
                ++pos;
            }
        }
        else if(std::isalpha(peek)){// [a-zA-Z][_a-zA-Z0-9]*
            // int off=1;
            // next=file[pos+off];
            // cur+=peek;
            next=peek;
            while(std::isalpha(next)||std::isdigit(next)||next=='_'){
                cur+=next;
                next=file[++pos];
            }
        }
        else if(std::isdigit(peek)){// [0-9]+(\.[0-9]+)?
            cur+=peek;
            while(pos!=file.size()&&std::isdigit(next=file[++pos])||next=='.'){
                if(next=='.'){
                    if(std::isdigit(file[pos+1])){
                        cur+='.';
                    }
                    else throw 0;
                }
                else cur+=next;
            }
        }
        else{}//error
        // output
        if(peek=='\"'){
            std::cout<<"< \","+getTokenType4out("\"")+">\n"
                <<"< "+cur+",literal>\n"<<"< \","+getTokenType4out("\"")+">"<<std::endl;
        }else{
            std::cout<<"< "+cur+","+getTokenType4out(cur)+">"<<std::endl;
        }cur.clear();
    }
}
size_t lexer_c::skipBlockComment(size_t p,std::string const&str){
    char next;
    while(next=str[p++]){
        if(next=='\n'){
            ++this->lines;
            this->byte=0;
        }else if(next=='*'){
            if(p!=str.size()){
                switch(str[p]){
                    case '/':
                        this->byte+=2;
                        return p+1;
                    default:
                        ++this->byte;
                        break;
                }
            }else this->err("block comment is open");
        }else ++this->byte;
    }
    if(!next)this->err("block comment is open");
    return p;
}
size_t lexer_c::skipLineComment(size_t p,std::string const&str){
    char next;
    while(next=str[p++]){
        if(next=='\n'){
            ++this->lines;
            this->byte=0;
            return p;
        }
    }return p;
}


lexer_c& lexer_c::execute(){// a mixture of full states machine and pre-fetch
    lexer_c::reload();
    int brace,bracket,parenthness,curly;// {} [] () <>
    brace=bracket=parenthness=curly=0;// curly is unable to judge when lexer
    lexicalState state=Null;
    std::string pass;
    size_t pos=0,np;// position and new position
    char here,next;// char get
    while(here=file[pos]/* ||('\0'==here&&pos==file.size()-1) */){
        pass+=here;
        if(this->debug)std::cout<<pos<<":"<<pass<<"|"<<state<<std::endl;
        // if(this->debug)this->err(file.substr(pos,1),true,true);
        bool close=false,getNext=false,jump=false;
        next=file[pos+1];
        if(state==Null){// sign will be deal next cycle
            switch(here){// todo deal the start machine
                case '\n':
                    ++this->lines;byte=0;
                    ++pos;pass.clear();continue;
                case '\"':
                    state=String;break;
                case '\'':
                    state=Character;break;
                case '{':
                    // if(this->debug)this->err("brace inc",true);
                    ++brace;break;
                case '}':
                    // if(this->debug)std::cout<<"before cut - brace:"<<brace<<std::endl;
                    if(--brace<0){
                        this->err("unexpected brace here");
                    }else break;
                case '[':
                    ++bracket;break;
                case ']':
                    if(--bracket<0){
                        this->err("unexpected bracket here");
                    }else break;
                case '(':
                    ++parenthness;break;
                case ')':
                    if(--parenthness<0){
                        this->err("unexpected parenthness here");
                    }else break;
                case '/':// comment or divisor
                    switch(next){
                        case '/':state=Comment;     continue;
                        case '*':state=BlockComment;continue;
                    }// just a divisor
                // <= >= == :: != ->
                case '<': case '>': case '=': case '!': case '+': case '-': case '*': case '%':
                    if(next=='='||('-'==here&&(next=='>'||'-'==next))||('+'==here&&'+'==next))
                    getNext=true;break;
                case ':':
                    if(next==':')getNext=true;break;
            }
            state=Sign;
            // pass+=here;
            if(getNext)pass+=next;// getNext
            // if(jump)continue;
            if(std::isspace(here)){
                pos=skipSpace(pos,file);
                pass.clear();state=Null;continue;}// space
            else if(std::ispunct(here)){// punctual
                if('_'==here)state=Identifier;
            }else if(std::isalpha(here)){
                state=Identifier;
            }else if(std::isdigit(here)){
                state=Number;
            }
        }
        std::string eMsg;
        switch(state){
            case Comment:
                pos=skipLineComment(pos,file);
                pass.clear();state=Null;continue;
            case BlockComment:
                pos=skipBlockComment(pos,file);
                pass.clear();state=Null;continue;
            case String:
                np=lexeme_process::getLiteralString(pos,file,pass)+1;
                eMsg="incorrect literal string";
                pass+='\"';break;
            case Character:// get the whole char
                np=lexeme_process::get_char(pos,file,pass)+1;
                eMsg="char get error";
                pass+='\'';break;
            case Identifier:
                np=lexeme_process::getIdentifier(pos,file,pass);
                eMsg="unacceptable identifier";
                break;
            case Number:
                np=lexeme_process::getNumber(pos,file,pass);
                eMsg="Number error";
                break;
            case Sign:
                np=pos+pass.size();break;
        }// lexemes collect
        lexemes.push_back(pass);
        pass.clear();
        state=Null;
        if(pos==np) this->err(eMsg);// error
        this->byte+=np-pos;
        // pos=np+(close?1:0);// close mark
        pos=np;
    }
    if(brace!=0)this->err("braces don't match");
    if(bracket!=0)this->err("bracket don't match");
    if(parenthness!=0)this->err("parenthness don't match");
    return *this;
}
#endif// _LEXER_C_HPP
